package org.zhtkj.framework;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.zhtkj.framework.codec.MessageDecoder;
import org.zhtkj.framework.codec.MessageEncoder;
import org.zhtkj.framework.mapping.Handler;
import org.zhtkj.framework.mapping.HandlerMapper;
import org.zhtkj.framework.message.Header;
import org.zhtkj.framework.message.PackageData;
import org.zhtkj.framework.session.Session;
import org.zhtkj.framework.session.SessionManager;
import org.zhtkj.web.utils.JT808ProtocolUtils;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;

@ChannelHandler.Sharable
public class TCPServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(TCPServerHandler.class.getSimpleName());

    private SessionManager sessionManager;
    
    private HandlerMapper handlerMapper;

    private MessageDecoder decoder;

    private MessageEncoder<?> encoder;

    private RedisTemplate<String, Object> redisTemplate;
    
    public TCPServerHandler(MessageDecoder decoder, MessageEncoder<?> encoder, HandlerMapper handlerMapper, SessionManager sessonManager, RedisTemplate<String, Object> redisTemplate) {
        this.decoder = decoder;
        this.encoder = encoder;
        this.handlerMapper = handlerMapper;
        this.redisTemplate = redisTemplate;
        this.sessionManager = sessonManager;
    }

	@SuppressWarnings({"rawtypes", "unchecked" })
	@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            ByteBuf headerBodyBuf = (ByteBuf) msg;
            if (headerBodyBuf.readableBytes() <= 0) {
            	return;
            }
            //反转义
            ByteBuf escapeBuf = JT808ProtocolUtils.doEscape4Receive(headerBodyBuf);
            //解码消息头
            Header header = decoder.decodeHeader(escapeBuf);
            //获取业务处理handler
            Handler handler = handlerMapper.getHandler(header.getType());
            if (handler != null) {
                Class<?>[] types = handler.getTargetParameterTypes();
                Class<? extends PackageData<?>> targetClass = (Class<? extends PackageData<? extends Header>>) types[0];
                PackageData body = decoder.decodeBody(escapeBuf.slice(header.getHeaderLength(), header.getBodyLength()), targetClass);
                body.setHeader(header);
                //更新session信息
                org.zhtkj.web.jt808.dto.basics.Header newHeader = (org.zhtkj.web.jt808.dto.basics.Header) body.getHeader();
                String key = "session:" + newHeader.getTerminalPhone();
                redisTemplate.opsForHash().put(key, "sessionId", Session.buildId(ctx.channel()));
                redisTemplate.expire(key, 30, TimeUnit.MINUTES);
                Object result;
                if (types.length == 1) {
                    result = handler.invoke(body);
                } else {
                	Session session = sessionManager.findBySessionId(Session.buildId(ctx.channel()));
                	if (session == null) {
                		session = Session.buildSession(ctx.channel());
                	}
                    result = handler.invoke(body, session);
                }
                logger.info("{}in, hex:{}\n{}" + ((result == null) ? "\n" : ""), handler, "7e" + ByteBufUtil.hexDump(headerBodyBuf) + "7e", body);
                if (result != null) {
                    ByteBuf resultBuf = JT808ProtocolUtils.doEscape4Send(encoder.encodeAll((PackageData) result));
                    ByteBuf allResultBuf = Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(new byte[]{0x7E}), resultBuf, Unpooled.wrappedBuffer(new byte[]{0x7E}));
                    logger.info("out hex:{}\n{}\n", ByteBufUtil.hexDump(allResultBuf), result);
                    ctx.channel().writeAndFlush(allResultBuf);
                }
            } else {
                logger.info("未知消息={}", header);
            }
        } catch(Exception e) {
        	e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    	sessionManager.put(Session.buildId(ctx.channel()), Session.buildSession(ctx.channel()));
        logger.info("终端建立连接:sessionId={}", ctx.channel().id().asLongText());
    }
    
	@Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		logger.error("终端连接异常:sessionId={}, 异常信息:{}", ctx.channel().id().asLongText(), cause.getMessage());
    }

	@Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		logger.info("终端断开连接:sessionId={}", ctx.channel().id().asLongText());
        sessionManager.removeBySessionId(ctx.channel().id().asLongText());
    }

	@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
            	logger.info("服务器主动断开连接:sessionId={}", ctx.channel().id().asLongText());
                sessionManager.removeBySessionId(ctx.channel().id().asLongText());
            }
        }
    }

}